/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.boot.util;

import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonWriteFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.deser.std.NumberDeserializers;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.iwindplus.boot.domain.constant.CommonConstant;
import com.iwindplus.boot.domain.constant.CommonConstant.ExceptionConstant;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.Assert;

/**
 * jackson工具类.
 *
 * @author zengdegui
 * @since 2024/11/22
 */
@Slf4j
public class JacksonUtil {

    private JacksonUtil() {
        throw new IllegalStateException(CommonConstant.UTILITY_CLASS);
    }

    private static ObjectMapper OBJECT_MAPPER;

    /**
     * 获取当前使用的 ObjectMapper 实例.
     *
     * @return ObjectMapper
     */
    public static ObjectMapper getObjectMapper() {
        if (OBJECT_MAPPER == null) {
            synchronized (JacksonUtil.class) {
                if (OBJECT_MAPPER == null) {
                    OBJECT_MAPPER = JacksonUtil.jackson2ObjectMapperBuilder(null).build();
                }
            }
        }
        return OBJECT_MAPPER;
    }

    /**
     * 设置 ObjectMapper.
     *
     * @param objectMapper objectMapper
     */
    public static void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "ObjectMapper should not be null");
        OBJECT_MAPPER = objectMapper;
    }

    /**
     * 将对象转换为字节数组.
     *
     * @param obj 对象
     * @return byte[]
     */
    public static byte[] toJsonBytes(Object obj) {
        try {
            return getObjectMapper().writeValueAsBytes(obj);
        } catch (JsonProcessingException ex) {
            log.warn(ExceptionConstant.JSON_PROCESSING_EXCEPTION, ex);
            return new byte[0];
        }
    }

    /**
     * 将对象转换为 JSON 字符串.
     *
     * @param obj 对象
     * @return String
     */
    public static String toJsonStr(Object obj) {
        try {
            return getObjectMapper().writeValueAsString(obj);
        } catch (JsonProcessingException ex) {
            log.error(ExceptionConstant.JSON_PROCESSING_EXCEPTION, ex);
            return null;
        }
    }

    /**
     * 格式化输出 JSON 字符串（带缩进）.
     *
     * @param obj 对象
     * @return String
     */
    public static String toJsonPrettyStr(Object obj) {
        try {
            return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (JsonProcessingException ex) {
            log.error(ExceptionConstant.JSON_PROCESSING_EXCEPTION, ex);
            return null;
        }
    }

    /**
     * 将对象转换为字节数组.
     *
     * @param obj 对象
     * @return byte[]
     */
    public static byte[] toJsonPrettyBytes(Object obj) {
        try {
            return JacksonUtil.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsBytes(obj);
        } catch (JsonProcessingException ex) {
            log.warn(ExceptionConstant.JSON_PROCESSING_EXCEPTION, ex);
            return new byte[0];
        }
    }

    /**
     * 将 字节数组转换为对象.
     *
     * @param bytes 字节
     * @param clazz 类
     * @param <T>   泛型
     * @return T
     */
    public static <T> T parseBytes(byte[] bytes, Class<T> clazz) {
        try {
            return JacksonUtil.getObjectMapper().readValue(bytes, clazz);
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
            return null;
        }
    }

    /**
     * 将 JSON 字符串转换为对象.
     *
     * @param text  JSON 字符串
     * @param clazz 目标类
     * @param <T>   泛型
     * @return T
     */
    public static <T> T parseObject(String text, Class<T> clazz) {
        if (text == null || clazz == null) {
            return null;
        }
        try {
            return getObjectMapper().readValue(text, clazz);
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
            return null;
        }
    }

    /**
     * 使用 TypeReference 解析 JSON 字符串.
     *
     * @param text          JSON 字符串
     * @param typeReference 类型引用
     * @param <T>           泛型
     * @return T
     */
    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
        if (text == null || typeReference == null) {
            return null;
        }
        try {
            return getObjectMapper().readValue(text, typeReference);
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
            return null;
        }
    }

    /**
     * 解析 JSON 字符串为 List<Map<String, Object>>.
     *
     * @param text JSON 字符串
     * @return List<Map < String, Object>>
     */
    public static List<Map<String, Object>> parseList(String text) {
        if (text == null) {
            return null;
        }
        try {
            return getObjectMapper().readValue(text,
                getObjectMapper().getTypeFactory().constructParametricType(List.class, Map.class));
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
            return null;
        }
    }

    /**
     * 解析 JSON 字符串为 JsonNode.
     *
     * @param text JSON 字符串
     * @return JsonNode
     */
    public static JsonNode parseTree(String text) {
        if (text == null) {
            return null;
        }
        try {
            return getObjectMapper().readTree(text);
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
            return null;
        }
    }

    /**
     * 创建 Jackson2ObjectMapperBuilder
     *
     * @param jacksonProperties jacksonProperties
     * @return Jackson2ObjectMapperBuilder
     */
    public static Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(JacksonProperties jacksonProperties) {
        final String dateFormat = Optional.ofNullable(jacksonProperties).map(JacksonProperties::getDateFormat)
            .orElse(DatePattern.NORM_DATETIME_PATTERN);
        final Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = new Jackson2ObjectMapperBuilder()
            .locale(Optional.ofNullable(jacksonProperties).map(JacksonProperties::getLocale).orElse(Locale.getDefault()))
            .timeZone(Optional.ofNullable(jacksonProperties).map(JacksonProperties::getTimeZone).orElse(TimeZone.getDefault()))
            .simpleDateFormat(dateFormat)
            .modules(JacksonUtil.buildModule(dateFormat))
            .serializationInclusion(JsonInclude.Include.NON_EMPTY)
            .serializationInclusion(JsonInclude.Include.NON_NULL)
            .featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING)
            .featuresToEnable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN)
            .featuresToEnable(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS)
            .featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
            .featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT)
            .featuresToEnable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
            .featuresToEnable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
            .featuresToEnable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)
            .featuresToEnable(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
            .featuresToEnable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS.mappedFeature())
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .featuresToDisable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
            .featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
            .featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        log.info("Jackson2ObjectMapperBuilder={}", jackson2ObjectMapperBuilder);
        return jackson2ObjectMapperBuilder;
    }

    private static List<Module> buildModule(String dateFormat) {
        final JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateFormat)));
        javaTimeModule.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
        javaTimeModule.addSerializer(Instant.class, InstantSerializer.INSTANCE);
        javaTimeModule.addDeserializer(BigDecimal.class, NumberDeserializers.BigDecimalDeserializer.instance);
        javaTimeModule.addSerializer(BigDecimal.class, new NumberSerializer(BigDecimal.class));
        return Arrays.asList(javaTimeModule);
    }
}

